<?php

namespace app\common\job;



use app\common\dict\LogLevelDict;
use app\common\dict\TaskStatusDict;
use app\common\dict\TaskTypeDict;
use app\common\exception\AdminApiException;
use app\common\model\QueueTasks;
use app\common\service\QueueTaskLogService;
use app\common\service\QueueTasksService;
use think\db\exception\DataNotFoundException;
use think\db\exception\DbException;
use think\db\exception\ModelNotFoundException;
use \think\facade\Queue;
use think\queue\Job;
use Throwable;

abstract class BaseQueueTask
{


    /**
     * 列队名称
     * @var string
     */
    const QUEUE_NAME = 'queue_task_base';

    /**
     * 任务重试次数
     * @var int
     */
    protected $attempts = 3;


    /**
     * 任务对应数据库中的记录ID
     * @var int
     */
    protected $jobId = 0;


    /**
     * 任务失败后再次执行的延迟时间，单位秒
     * @var int
     */
    protected $delay = 120;



    /**
     * 数据库模型
     * @var QueueTasks
     */
    protected $model;



    /**
     * 创建实例 ，实现代码：return self::class;
     * @return string
     */
    abstract static function createInstance(): string;


    /**
     * 开始执行
     * @param Job $job
     * @param $data
     * @return bool
     */
    public function fire(Job $job, $data)
    {

        $this->job = $job;

        $this->attempts = get_sys_config('task_attempts') ?? 3;
        $this->delay = get_sys_config('task_delay') ?? 120;

        $this->jobId = $data['task_id'] ?? 0;
        if(!$this->jobId){
            $job->delete();
            $this->failed($data);
            return false;
        }

        $this->model = new QueueTasks();
        $this->model = $this->model->find($this->jobId);

        if(!$this->model){
            $job->delete();
            $this->failed($data);
            trace('模型不存在，任务取消！任务ID：' . $this->jobId, 'job-debug');
            return false;
        }


        try {
            $this->clearDbLog(get_sys_config('task_log_retention_days') ?? 15);
        }catch (Throwable $e){
            $this->log('日志清理失败，原因：' . $e->getMessage(), LogLevelDict::ERROR);
        }


        if($this->model->status == TaskStatusDict::SUCCESS){
            $job->delete();
            return false;
        }

        //开始执行
        $this->start();

        //调试模式
        if(env('APP_DEBUG', false)){
            $this->log('收到任务参数：' . json_encode($data), LogLevelDict::DEBUG);
        }

        try {
            if($this->doJob($data)){
                $job->delete();
                $this->success();
            }else{
                if($job->attempts() >= $this->attempts){
                    $job->delete();
                    $this->fail('执行失败已达最大重试次数，任务取消！');
                    $this->failed($data);
                }else{
                    // 如果 任务没有被删除，则重新发布任务
                    if(!$job->isDeleted()){
                        $this->log("任务执行失败，正在等待{$this->delay}秒后重试...", LogLevelDict::WARNING);
                        $job->release($this->delay);
                    }
                }
            }
        }catch (Throwable $e){
            $job->delete();
            $this->fail($e->getMessage());
            //调试模式
            if(env('APP_DEBUG', false)){
                $this->log(json_encode($e->getTrace()), LogLevelDict::DEBUG);
            }
            return false;
        }

        return true;
    }



    /**
     * 计划任务启动方法
     * @param $data
     * @return bool
     */
    abstract function doJob($data):bool;


    /**
     * 任务执行失败的方法
     * @return mixed
     */
    abstract function failed($data);


    protected function start($msg = '开始执行'): bool
    {
        return $this->log($msg, LogLevelDict::INFO, TaskStatusDict::RUNNING);
    }


    protected function fail($msg = '执行失败'): bool
    {
        return $this->log($msg, LogLevelDict::ERROR, TaskStatusDict::FAILURE);
    }


    protected function success($msg = '执行成功'): bool
    {
        return $this->log($msg, LogLevelDict::SUCCESS, TaskStatusDict::SUCCESS);
    }



    private function clearDbLog(int $day = 15): void
    {
        if($this->model){
            // 清理日志
            $this->model->whereTime('start_time','<',strtotime("-$day day"))->delete();
        }
    }



    /**
     * 记录日志
     * @param string $msg 消息内容
     * @param string $level 日志级别
     * @param int|null $status 设置任务状态
     * @return bool
     */
    protected function log(string $msg, string $level = LogLevelDict::INFO, int|null $status = null): bool
    {
        if(!$this->model){
            return false;
        }
        $this->model->addLog($msg, $level);

        // 设置任务状态
        if($status != null){
            if($status == TaskStatusDict::RUNNING){
                $this->model->start_time = time();
            }
            if(in_array($status , [TaskStatusDict::FAILURE, TaskStatusDict::SUCCESS])){
                $this->model->end_time = time();
            }

            $this->model->status = $status;
            return $this->model->save();
        }
        return true;
    }


    /**
     * 任务创建
     * @param int $site_id 站点ID
     * @param string $title 任务标题
     * @param int $admin_id 创建人
     * @param array $data 任务参数
     * @param int $type 任务类型，使用TaskTypeDict
     * @param int $delay 延迟时间，默认0秒
     * @return bool
     */
    public static function dispatch(int $site_id, string $title, int $admin_id = 0, array $data = [], int $type = TaskTypeDict::SYSTEM, int $delay = 0): bool
    {
        $task_id = (new QueueTasksService())->add([
            'site_id' => $site_id,
            'type' => $type,
            'admin_id' => $admin_id,
            'title' => $title,
            'class_name' => static::createInstance(),
            'create_time' => time(),
            'params' => json_encode($data),
        ]);
        if(!$task_id) throw new AdminApiException('创建任务失败！');

        //初始的日志
        (new QueueTaskLogService())->add([
            'task_id' => $task_id,
            'site_id' => $site_id,
            'message' => $delay == 0 ? '任务已创建，正在等待执行...': "任务已创建，预计" . date('Y-m-d H:i:s',time() + $delay) . "后执行...",
            'level' => LogLevelDict::INFO,
            'create_time' => time()
        ]);

        if($delay == 0){
            return Queue::push(static::createInstance(), array_merge($data, ['task_id' => $task_id]), static::QUEUE_NAME);
        }
        return Queue::later($delay, static::createInstance(), array_merge($data, ['task_id' => $task_id]), static::QUEUE_NAME);
    }


    /**
     * 重新发布任务
     * @param int $job_id
     * @return mixed|null
     */
    public static function republish(int $job_id): mixed
    {
        $model = new QueueTasks();
        try {
            $job = $model->find($job_id);
        } catch (DataNotFoundException|ModelNotFoundException|DbException $e) {
            throw new AdminApiException('数据库异常！');
        }
        if(!$job){
            throw new AdminApiException('未找到任务数据！');
        }
        $data = json_decode($job->job_params,true);
        $data['task_id'] = $job_id;
        return Queue::push(static::createInstance(), $data, static::QUEUE_NAME);
    }



}